package sky.ikaros.android_plugin_log;

import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AndroidPluginLog extends AndroidPluginLogAbs{
    private  static final String TAG = "AndroidPluginLog";
    private static final String FILE_NAME = "AndroidLog";
    private static final String FILE_SUFFIX = ".log";
    private static final String LOG_FILE_NAME = "logs";
    private static final long FILE_SIZE = 1024 * 1024L; // 文件大小
    private ExecutorService es;
    private boolean showMessageOnConsole = false;
    private Context mContext;
    private File mFile;
    private AndroidLogLevel mLevel;
    public void register(Context context){
        register(context, true);
    }
    public void register(Context context, boolean showMessageOnConsole){
        register(context, showMessageOnConsole, AndroidLogLevel.DEBUG);
    }
    public void register(Context context, boolean showMessageOnConsole, AndroidLogLevel level){
        mContext = context;
        mLevel = level;
        this.showMessageOnConsole = showMessageOnConsole;
        createDirectoryIfNotExist();
        if(es == null) es = Executors.newSingleThreadExecutor();
    }
    private int getFileListNum(){
        if(mFile == null) return -1;
        File[] files = mFile.listFiles();
        if(files == null) return 0;
        return files.length;
    }
    private boolean createDirectoryIfNotExist(){
        if(mFile == null) {
            mFile = mContext.getExternalFilesDir(LOG_FILE_NAME);
            if(mFile != null) Log.w(TAG, String.format("the log directory is %s", mFile.getAbsolutePath()));
            else Log.e(TAG, "the log directory create fail");
            return mFile != null;
        }
        if(!mFile.exists() || !mFile.isDirectory()){
            boolean created= mFile.mkdirs();
            Log.w(TAG, String.format("result of creating log directory is %s, the path is %s", created, mFile.getAbsolutePath()));
            return created;
        }
        return true;
    }
    private File getLogFile(){
        return new File(mFile, FILE_NAME + FILE_SUFFIX);
    }
    private boolean createNewFileIfNotExist(){
        File file = new File(mFile, FILE_NAME + FILE_SUFFIX);
        if(!file.exists()) {
            try {
                boolean created = file.createNewFile();
                Log.w(TAG, String.format("result of creating log file is %s, file name is %s", created, file.getName()));
                return created;
            } catch (IOException e) {
                Log.e(TAG, "creating log file error, file name is " + file.getName(), e);
            }
            return false;
        }
        return true;
    }
    private boolean checkFileSizeIfEnough(){
        File file = getLogFile();
        return file.length() < FILE_SIZE;
    }
    private void clearFileContent(){
        File file = getLogFile();
        try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
            raf.setLength(0);
            Log.w(TAG, String.format("the log file clear successfully, rename is %s", file.getName()));
        } catch (IOException e) {
            Log.e(TAG, "the log file clear fail, file name is " + file.getName(), e);
        }
    }
    private void renameAndCreateNewFile(){
        File source = getLogFile();
        int num = getFileListNum();
        File file = new File(mFile, FILE_NAME + "." + (num - 1) + FILE_SUFFIX);
        boolean rename = source.renameTo(file);
        Log.w(TAG, String.format("the log file rename result is %s, rename is %s", rename, file.getName()));
        if(rename){
            boolean readOnly = file.setReadable(true);
            Log.w(TAG, String.format("the log file set readonly result is %s, the file name is %s", readOnly, file.getName()));
            clearFileContent();
        }
    }
    private boolean check(){
        boolean exist = createDirectoryIfNotExist();
        if(!exist) return false;
        boolean result = createNewFileIfNotExist();
        if(!result) return false;
        boolean isEnough = checkFileSizeIfEnough();
        if(!isEnough) renameAndCreateNewFile();
        return true;
    }
    private void writeMessageToFile(String type, String tag, String message, Throwable t){
        File file = getLogFile();
        try(OutputStream out = new FileOutputStream(file, true)){
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
            String date = sdf.format(new Date());
            String result = "";
            if (t != null) {
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                t.printStackTrace(pw);
                result = sw.toString();
            }
            String str = String.format("[%s] [%s] [%s] %s %s\r\n", date, type, tag, message, result);
            out.write(str.getBytes(StandardCharsets.UTF_8));
            out.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public String getLogDir(){
        if(mFile == null) return null;
        return mFile.getAbsolutePath();
    }
    @Override
    public File[] getChildren(){
        if(mFile == null) return null;
        return mFile.listFiles();
    }
    @Override
    public void trace(String tag, String message, Throwable t) {
        if(mLevel.get() > AndroidLogLevel.TRACE.get()) return;
        es.execute(()->{
            synchronized (AndroidPluginLog.class){
                boolean result = check();
                if(!result) {
                    if(showMessageOnConsole) Log.v(tag, message, t);
                    return;
                }
                writeMessageToFile("TRACE", tag, message, t);
                if(showMessageOnConsole) Log.v(tag, message, t);
            }
        });
    }
    @Override
    public void debug(String tag, String message, Throwable t) {
        if(mLevel.get() > AndroidLogLevel.DEBUG.get()) return;
        es.execute(()->{
            synchronized (AndroidPluginLog.class){
                boolean result = check();
                if(!result) {
                    if(showMessageOnConsole) Log.d(tag, message, t);
                    return;
                }
                writeMessageToFile("DEBUG", tag, message, t);
                if(showMessageOnConsole) Log.d(tag, message, t);
            }
        });
    }

    @Override
    public void warn(String tag, String message, Throwable t) {
        if(mLevel.get() > AndroidLogLevel.WARN.get()) return;
        es.execute(()->{
            synchronized (AndroidPluginLog.class){
                boolean result = check();
                if(!result) {
                    if(showMessageOnConsole) Log.w(tag, message, t);
                    return;
                }
                writeMessageToFile(" WARN", tag, message, t);
                if(showMessageOnConsole) Log.w(tag, message, t);
            }
        });
    }

    @Override
    public void info(String tag, String message, Throwable t) {
        if(mLevel.get() > AndroidLogLevel.INFO.get()) return;
        es.execute(()->{
            synchronized (AndroidPluginLog.class){
                boolean result = check();
                if(!result) {
                    if(showMessageOnConsole) Log.w(tag, message, t);
                    return;
                }
                writeMessageToFile(" INFO", tag, message, t);
                if(showMessageOnConsole) Log.i(tag, message, t);
            }
        });
    }

    @Override
    public void error(String tag, String message, Throwable t) {
        if(mLevel.get() > AndroidLogLevel.ERROR.get()) return;
        es.execute(()->{
            synchronized (AndroidPluginLog.class){
                boolean result = check();
                if(!result) {
                    if(showMessageOnConsole) Log.w(tag, message, t);
                    return;
                }
                writeMessageToFile("ERROR", tag, message, t);
                if(showMessageOnConsole) Log.e(tag, message, t);
            }
        });
    }
    public void release(){
        if(es != null) {
            while (!es.isTerminated()){
                try {
                    Thread.sleep(10L);
                } catch (InterruptedException ignored) { }
            }
            es.shutdown();
            es = null;
        }
        mFile = null;
        mContext = null;
        showMessageOnConsole = false;
    }
}
